/*
	d3d9cutter.cpp: Direct3D proxy code for rotating the screen (amungst other things)

	Copyright (c) 2010, James Lee (jbit<at>jbit<dot>net)
	All rights reserved.

	Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

	Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
	Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
	Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.	
*/
#define WIN32_LEAN_AND_MEAN		
#define _CRT_SECURE_NO_WARNINGS // C isn't that dangerous...
#include <windows.h>
#include <d3d9.h>
#include <stdio.h>
#include <string.h>
#include "d3d9cutter.h"
#include "cutter.h"

typedef IDirect3D9 *(WINAPI* Direct3DCreate9Func)(UINT SDKVersion);

IDirect3D9* WINAPI Direct3DCreate9(UINT SDKVersion);

static HINSTANCE g_D3DDLL = NULL;
static Direct3DCreate9Func g_RealDirect3DCreate9 = NULL;
static CutterParams *g_CutterParams = NULL;
static char g_GameName[128] = "";

#define GAMEID_TOUHOU10  0x100
#define GAMEID_TOUHOU11  0x110
#define GAMEID_TOUHOU12  0x120
#define GAMEID_TOUHOU128 0x128
static int  g_GameID = -1;

static void ResolveGameName()
{
	char buf[512] = "";
	::GetModuleFileName((HINSTANCE)0, buf, sizeof(buf));
	char *exeFile = strrchr(buf, '\\');
	if (exeFile!=NULL)
		exeFile++;

	char *tmp = strrchr(buf, '/');
	if (tmp!=NULL)
		exeFile = tmp+1;

	tmp = strrchr(buf, '.');
	if (tmp)
		tmp[0] = '\0';

	OutputDebugPrintf("I am %s\n", exeFile);
	_snprintf(g_GameName, sizeof(g_GameName), "%s", exeFile);
	if      (!_stricmp(exeFile, "th128") || !_stricmp(exeFile, "th128e"))
		g_GameID = GAMEID_TOUHOU128;
	else if (!_stricmp(exeFile, "th12") || !_stricmp(exeFile, "th12e"))
		g_GameID = GAMEID_TOUHOU12;
	else if (!_stricmp(exeFile, "th11") || !_stricmp(exeFile, "th11e"))
		g_GameID = GAMEID_TOUHOU11;
	else if (!_stricmp(exeFile, "th10") || !_stricmp(exeFile, "th10e"))
		g_GameID = GAMEID_TOUHOU10;

}

BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, LPVOID lpReserved)
{
	(void)hModule;
	(void)lpReserved;

	switch(dwReason)
	{
	case DLL_PROCESS_ATTACH:
	{
		ResolveGameName();
		char path[1024] = "";
		_snprintf(path, sizeof(path), "%s.cutter", g_GameName);
		g_CutterParams = cutterParamsRead(path);

		if (g_CutterParams)
			_snprintf(path, sizeof(path), "%s", g_CutterParams->dllPath);

		if (path[0]=='\0')
		{
			char sysdir[MAX_PATH];
			::GetSystemDirectory(sysdir, MAX_PATH);
			_snprintf(path, sizeof(path), "%s\\d3d9.dll", sysdir);
		}

		g_D3DDLL = ::LoadLibrary(path);
		if (!g_D3DDLL)
		{
			OutputDebugPrintf("%s: Failed to load DLL!\n", path);
			return FALSE;
		}
		
		g_RealDirect3DCreate9 = (Direct3DCreate9Func)GetProcAddress(g_D3DDLL, "Direct3DCreate9");
		if (!g_RealDirect3DCreate9) 
		{
			OutputDebugPrintf("%s: Doesn't contain Direct3DCreate9!\n", path);
			::FreeLibrary(g_D3DDLL);
			g_D3DDLL = NULL;
			return FALSE;
		}

	
		break;
	}
	case DLL_PROCESS_DETACH:
		if (g_D3DDLL)
		{
			::FreeLibrary(g_D3DDLL);
			g_D3DDLL = NULL;  
		}
		break;
	default:
		break;
	}
	return TRUE;
}

IDirect3D9* WINAPI Direct3DCreate9(UINT SDKVersion)
{
	if (g_RealDirect3DCreate9 == NULL)
		return NULL;

	IDirect3D9 *ret = g_RealDirect3DCreate9(SDKVersion);
	if (ret == NULL)
		return NULL;

	// if there's no parameter file, just act normal!
	if (g_CutterParams==NULL)
		return ret;

	return new IDirect3D9Cutter(ret);
}



////////////////////////////////////////
// IDirect3D9Cutter
////////////////////////////////////////

HRESULT __stdcall IDirect3D9Cutter::QueryInterface(REFIID riid, void** ppvObj)
{
	HRESULT result = m_real->QueryInterface(riid, ppvObj); 
	if (result == S_OK)
		*ppvObj = this;	
	return result;
}

ULONG __stdcall IDirect3D9Cutter::AddRef(void)
{
	return m_real->AddRef();
}

ULONG __stdcall IDirect3D9Cutter::Release(void)
{
	ULONG count = m_real->Release();
	if (!count) 
		delete this; 
	return count;
}

HRESULT __stdcall IDirect3D9Cutter::CreateDevice(UINT Adapter, D3DDEVTYPE DeviceType, HWND hFocusWindow, DWORD BehaviorFlags, D3DPRESENT_PARAMETERS* pPresentationParameters, IDirect3DDevice9** ppReturnedDeviceInterface)
{
	HRESULT result = m_real->CreateDevice(Adapter, DeviceType, hFocusWindow, BehaviorFlags|D3DCREATE_MULTITHREADED, pPresentationParameters, ppReturnedDeviceInterface);
	*ppReturnedDeviceInterface = new IDirect3DDevice9Cutter(*ppReturnedDeviceInterface);
	return result;
}



////////////////////////////////////////
// IDirect3DDevice9Cutter
////////////////////////////////////////

typedef struct
{
	float x,y,z,w;
	float u,v;
} MyVert;
#define MYVERT_FVF D3DFVF_XYZRHW |D3DFVF_TEX1


IDirect3DDevice9Cutter::IDirect3DDevice9Cutter(IDirect3DDevice9* real) : m_real(real)
{
	IDirect3DSurface9 *backBuffer;
	m_real->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &backBuffer);

	D3DSURFACE_DESC desc;
	backBuffer->GetDesc(&desc);

	HRESULT result;
	result = m_real->CreateTexture(desc.Width, desc.Height, 1, desc.Usage, desc.Format, D3DPOOL_DEFAULT, &m_renderTexture, NULL);
	if (result != D3D_OK)
	{
		switch(result)
		{
		case D3DERR_INVALIDCALL: OutputDebugPrintf("CreateTexture FAILED D3DERR_INVALIDCALL\n"); break;
		case D3DERR_OUTOFVIDEOMEMORY: OutputDebugPrintf("CreateTexture FAILED D3DERR_OUTOFVIDEOMEMORY\n"); break;
		case E_OUTOFMEMORY: OutputDebugPrintf("CreateTexture FAILED E_OUTOFMEMORY\n"); break;
		default: OutputDebugPrintf("CreateTexture FAILED %08x\n", result);
		}
		exit(0);
	}
	m_width = desc.Width;
	m_height = desc.Height;
	m_renderTexture->GetSurfaceLevel(0, &m_renderSurface);

	m_real->SetRenderTarget(0, m_renderSurface);
	
	m_autoGame = m_autoMenu = -1;

	float texW = (1.0f/m_width);
	float texH = (1.0f/m_height);
	m_vertexBufferCount = 0;
	for (int i=0;i<g_CutterParams->configCount;i++)
	{
		int spriteCount = g_CutterParams->configList[i].blitEnd-g_CutterParams->configList[i].blitStart;
		if (spriteCount<=0)
		{
			// empty config
			continue;
		}

		m_vertexBufferFlags[m_vertexBufferCount] = g_CutterParams->configList[i].flags;

		m_real->CreateVertexBuffer(spriteCount*6*sizeof(MyVert), D3DUSAGE_WRITEONLY, MYVERT_FVF, D3DPOOL_MANAGED, &m_vertexBuffer[m_vertexBufferCount], NULL);
		MyVert *verts=NULL;
		m_vertexBuffer[m_vertexBufferCount]->Lock(0, 0, (void**)&verts, 0);
		int rot = g_CutterParams->configList[i].rot;
		for (int j=0;j<spriteCount;j++)
		{
			MyVert *d = &verts[j*6];
			int idx = j+g_CutterParams->configList[i].blitStart;
			float dx = (float)g_CutterParams->blitList[idx].dx;
			float dy = (float)g_CutterParams->blitList[idx].dy;
			float dw = (float)g_CutterParams->blitList[idx].dw;
			float dh = (float)g_CutterParams->blitList[idx].dh;
			float sx = (float)g_CutterParams->blitList[idx].sx;
			float sy = (float)g_CutterParams->blitList[idx].sy;
			float sw = (float)g_CutterParams->blitList[idx].sw;
			float sh = (float)g_CutterParams->blitList[idx].sh;


			d[0].z = d[1].z = d[2].z = d[3].z = d[4].z = d[5].z =-1.0f;
			d[0].w = d[1].w = d[2].w = d[3].w = d[4].w = d[5].w = 1.0f;

			// This is awesome:
			switch (rot)
			{
			case CUTTER_ROT_FULL:
				dx = -dx;
				dy = -dy;
				d[0].x = dx+dw-0.5f; d[0].y = dy   -0.5f; d[0].u = (sx   )*texW; d[0].v = (sy+sh)*texH;
				d[1].x = dx+dw-0.5f; d[1].y = dy+dh-0.5f; d[1].u = (sx   )*texW; d[1].v = (sy   )*texH;
				d[2].x = dx   -0.5f; d[2].y = dy   -0.5f; d[2].u = (sx+sw)*texW; d[2].v = (sy+sh)*texH;
				d[3].x = dx   -0.5f; d[3].y = dy   -0.5f; d[3].u = (sx+sw)*texW; d[3].v = (sy+sh)*texH;
				d[4].x = dx+dw-0.5f; d[4].y = dy+dh-0.5f; d[4].u = (sx   )*texW; d[4].v = (sy   )*texH;
				d[5].x = dx   -0.5f; d[5].y = dy+dh-0.5f; d[5].u = (sx+sw)*texW; d[5].v = (sy   )*texH;
				break;
			case CUTTER_ROT_CW:
				dx = m_height - dx;
				d[0].x = dy   -0.5f; d[0].y = dx   -0.5f; d[0].u = (sx   )*texW; d[0].v = (sy   )*texH;
				d[1].x = dy   -0.5f; d[1].y = dx-dw-0.5f; d[1].u = (sx+sw)*texW; d[1].v = (sy   )*texH;
				d[2].x = dy+dh-0.5f; d[2].y = dx   -0.5f; d[2].u = (sx   )*texW; d[2].v = (sy+sh)*texH;
				d[3].x = dy+dh-0.5f; d[3].y = dx   -0.5f; d[3].u = (sx   )*texW; d[3].v = (sy+sh)*texH;
				d[4].x = dy   -0.5f; d[4].y = dx-dw-0.5f; d[4].u = (sx+sw)*texW; d[4].v = (sy   )*texH;
				d[5].x = dy+dh-0.5f; d[5].y = dx-dw-0.5f; d[5].u = (sx+sw)*texW; d[5].v = (sy+sh)*texH;
				break;
			case CUTTER_ROT_CCW:
				dy = m_width - dy;
				d[0].x = dy   -0.5f; d[0].y = dx   -0.5f; d[0].u = (sx   )*texW; d[0].v = (sy   )*texH;
				d[1].x = dy   -0.5f; d[1].y = dx+dw-0.5f; d[1].u = (sx+sw)*texW; d[1].v = (sy   )*texH;
				d[2].x = dy-dh-0.5f; d[2].y = dx   -0.5f; d[2].u = (sx   )*texW; d[2].v = (sy+sh)*texH;
				d[3].x = dy-dh-0.5f; d[3].y = dx   -0.5f; d[3].u = (sx   )*texW; d[3].v = (sy+sh)*texH;
				d[4].x = dy   -0.5f; d[4].y = dx+dw-0.5f; d[4].u = (sx+sw)*texW; d[4].v = (sy   )*texH;
				d[5].x = dy-dh-0.5f; d[5].y = dx+dw-0.5f; d[5].u = (sx+sw)*texW; d[5].v = (sy+sh)*texH;
				break;
			case CUTTER_ROT_NONE:
			default:
				d[0].x = dx   -0.5f; d[0].y = dy+dh-0.5f; d[0].u = (sx   )*texW; d[0].v = (sy+sh)*texH;
				d[1].x = dx   -0.5f; d[1].y = dy-   0.5f; d[1].u = (sx   )*texW; d[1].v = (sy   )*texH;
				d[2].x = dx+dw-0.5f; d[2].y = dy+dh-0.5f; d[2].u = (sx+sw)*texW; d[2].v = (sy+sh)*texH;
				d[3].x = dx+dw-0.5f; d[3].y = dy+dh-0.5f; d[3].u = (sx+sw)*texW; d[3].v = (sy+sh)*texH;
				d[4].x = dx   -0.5f; d[4].y = dy   -0.5f; d[4].u = (sx   )*texW; d[4].v = (sy   )*texH;
				d[5].x = dx+dw-0.5f; d[5].y = dy   -0.5f; d[5].u = (sx+sw)*texW; d[5].v = (sy   )*texH;
				break;
			}
		}
		m_vertexBuffer[m_vertexBufferCount]->Unlock();
		m_vertexBufferSize[m_vertexBufferCount] = spriteCount*2;
		if (g_CutterParams->configList[i].flags&CUTTER_FLAG_AUTO_GAME)
			m_autoGame = m_vertexBufferCount;
		if (g_CutterParams->configList[i].flags&CUTTER_FLAG_AUTO_MENU)
			m_autoMenu = m_vertexBufferCount;
		m_vertexBufferCount++;
	}

	CreateStateBlock(D3DSBT_ALL, &m_stateBlock);
}

IDirect3DDevice9Cutter::~IDirect3DDevice9Cutter(void)
{
}

HRESULT __stdcall IDirect3DDevice9Cutter::QueryInterface (REFIID riid, void** ppvObj)
{
	HRESULT result = m_real->QueryInterface(riid, ppvObj); 
	if (result == S_OK)
		*ppvObj = this;
	return result;
}

ULONG __stdcall IDirect3DDevice9Cutter::AddRef(void)
{
	return m_real->AddRef();
}

ULONG __stdcall IDirect3DDevice9Cutter::Release(void)
{
	ULONG count = m_real->Release();
	if (!count)
		delete this;

	return count;
}

HRESULT __stdcall IDirect3DDevice9Cutter::Present(CONST RECT* pSourceRect,CONST RECT* pDestRect,HWND hDestWindowOverride,CONST RGNDATA* pDirtyRegion)
{
	HRESULT result = m_real->Present(pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion);
	return result;
}

HRESULT __stdcall IDirect3DDevice9Cutter::GetBackBuffer(UINT iSwapChain,UINT iBackBuffer,D3DBACKBUFFER_TYPE Type,IDirect3DSurface9** ppBackBuffer)
{
	HRESULT ret = m_real->GetBackBuffer(iSwapChain,iBackBuffer,Type,ppBackBuffer);
	if (ret==D3D_OK)
	{
		(*ppBackBuffer)->Release();
		m_renderSurface->AddRef();
		*ppBackBuffer = m_renderSurface;
	}
	return ret;
}

HRESULT __stdcall IDirect3DDevice9Cutter::EndScene(void)
{
	m_stateBlock->Capture();

	switch(g_GameID)
	{
	case GAMEID_TOUHOU10:
	case GAMEID_TOUHOU11:
	case GAMEID_TOUHOU12:
	case GAMEID_TOUHOU128:
	{
		// Super hack:
		// We can detect if the game is in the frontend or in gameplay based on how many clears it does per frame...
		if (m_clearCount != m_lastCount)
		{
			if (m_clearCount>5)
			{
				if (m_autoGame>=0)
					m_curMode = m_autoGame;
			}
			else
			{
				if (m_autoMenu>=0)
					m_curMode = m_autoMenu;
			}
		}
		m_lastCount = m_clearCount;
	}
	}

	m_clearCount=0;

	// change configuration when pause is pressed
	if ((GetAsyncKeyState(VK_PAUSE) & 0x0001)!=0)
		m_curMode++;

	if (m_curMode>=m_vertexBufferCount)
		m_curMode = 0;

	IDirect3DSurface9 *backBuffer;
	m_real->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &backBuffer);
	m_real->SetRenderTarget(0, backBuffer);

	m_real->SetRenderState(D3DRS_ZENABLE,          D3DZB_FALSE);
	m_real->SetRenderState(D3DRS_ZFUNC,            D3DCMP_ALWAYS);
	m_real->SetRenderState(D3DRS_ZWRITEENABLE,     FALSE);
	m_real->SetRenderState(D3DRS_ALPHATESTENABLE,  FALSE);
	m_real->SetRenderState(D3DRS_CULLMODE,         D3DCULL_NONE);
	m_real->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
	m_real->SetRenderState(D3DRS_SPECULARENABLE,   FALSE);
	m_real->SetRenderState(D3DRS_SPECULARENABLE,   FALSE);
	m_real->SetRenderState(D3DRS_SPECULARENABLE,   FALSE);
	m_real->SetRenderState(D3DRS_STENCILENABLE,    FALSE);

	m_real->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
	m_real->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
	m_real->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
	m_real->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
	m_real->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
	m_real->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
	m_real->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);
	m_real->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);

	m_real->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_ARGB(0,0,0,0), 1.0f, 0);


	D3DVIEWPORT9 view;
	view.X = 0;
	view.Y = 0;
	view.Width  = m_width;
	view.Height = m_height;
	view.MinZ = 0;
	view.MaxZ = 1;
	m_real->SetViewport(&view);


	// Not really required:
	D3DMATRIX mat;
	mat.m[0][0] = 1.0f; mat.m[0][1] = 0.0f; mat.m[0][2] = 0.0f; mat.m[0][3] = 0.0f;
	mat.m[1][0] = 0.0f; mat.m[1][1] = 1.0f; mat.m[1][2] = 0.0f; mat.m[1][3] = 0.0f;
	mat.m[2][0] = 0.0f; mat.m[2][1] = 0.0f; mat.m[2][2] = 1.0f; mat.m[2][3] = 0.0f;
	mat.m[3][0] = 0.0f; mat.m[3][1] = 0.0f; mat.m[3][2] = 0.0f; mat.m[3][3] = 1.0f;
	m_real->SetTransform(D3DTS_WORLD, &mat);
	m_real->SetTransform(D3DTS_PROJECTION, &mat);
	m_real->SetTransform(D3DTS_VIEW, &mat);

	// Use automagic shaders:
	m_real->SetVertexShader(NULL);
	m_real->SetPixelShader(NULL);
	m_real->SetFVF(MYVERT_FVF);

	if (m_vertexBufferFlags[m_curMode]&CUTTER_FLAG_LINEAR)
	{
		m_real->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
		m_real->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
		m_real->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);
	}
	else
	{
		m_real->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_POINT);
		m_real->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_POINT);
		m_real->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_POINT);
	}

	m_real->SetTextureStageState(0,D3DTSS_COLOROP,   D3DTOP_SELECTARG1);
	m_real->SetTextureStageState(0,D3DTSS_COLORARG1, D3DTA_TEXTURE);
	m_real->SetTextureStageState(0,D3DTSS_ALPHAOP,   D3DTOP_DISABLE);
	m_real->SetTexture(0, m_renderTexture);

	if (m_vertexBufferCount)
	{
		m_real->SetStreamSource(0, m_vertexBuffer[m_curMode], 0, sizeof(MyVert));
		m_real->DrawPrimitive(D3DPT_TRIANGLELIST, 0, m_vertexBufferSize[m_curMode]);
	}

	m_real->EndScene();
	m_stateBlock->Apply();
	m_real->SetRenderTarget(0, m_renderSurface);
	return 0;
}